﻿/**
 * @author alex
 */

Namespace("sketch.framework");

(function() {
    // usings
    var fwk = sketch.framework;

    fwk.Properties = klass(null, (function() {
        var validateProperty = function(name) {
            if (!this.data.hasOwnProperty(name)) {
                throw {
                    name:"ArgumentException",
                    description : "property (" + name + ") not found"
                };
            }
            ;
        };

        var iterateProperties = function(bag, callback, prefix) {
            for (var i in bag.data) {
                var fullName = prefix;
                var property = bag.data[i];

                if (fullName === undefined) {
                    fullName = property.propertyName;
                }
                else {
                    fullName += "." + property.propertyName;
                }

                var isComplex = property.defaultValue instanceof fwk.Properties;
                callback(property, isComplex, fullName);

                if (isComplex) {
                    iterateProperties(bag.get(property.propertyName), callback, fullName);
                }
            }
        };

        var bindToParentBagIfNecessary = function(bag, value) {
            if (value && value instanceof fwk.Properties) {
                value.propertyChanged.bind(bag.propertyChanged);
            }
        };

        return {
            _constructor: function(owner) {
                this.data = {};
                this.propertyChanged = fwk.EventHelper.createEvent();
                this._notifications = null;
                this._owner = owner;
            },
            createProperty:function(/*str*/name, /*str*/description, /*T*/defaultValue) {
                var that = this;
                var prop = function(/*any*/value) {
                    if (arguments.length === 1 && arguments[0] !== undefined) {
                        var oldValue = prop.value;

                        var event = createEvent(name, oldValue, value);

                        if (prop._validateCallback) {
                            prop._validateCallback(event);
                            if (event.ok) {
                                prop.value = value;
                                each(prop.validators, function(v){ v.clear(); });
                            }
                            else {
                                prop.value = event.changedValue !== undefined ? event.changedValue : oldValue;
                            }
                        } else {
                            prop.value = value;
                        }

                        bindToParentBagIfNecessary(that, value);

                        if (that._notifications === null) {
                            prop.changedEvent(event);
                            that.propertyChanged([event]);
                        } else {
                            that._notifications.push(event);
                        }
                    }
                    return prop.value;
                };

                var createEvent = function(name, oldValue, newValue){
                    return {
                        property:name,
                        newValue:newValue,
                        oldValue:oldValue,
                        sender: prop.getOwner(),
                        ok: true,
                        changedValue: undefined
                    };
                };

                prop.propertyName = name;
                prop.description = description;
                prop.value = defaultValue;
                prop.defaultValue = defaultValue;
                prop.changedEvent = fwk.EventHelper.createEvent();
                prop.validators = [];

                bindToParentBagIfNecessary(that, defaultValue);

                prop.bindingPoint = function(event) {
                    prop.call(prop, event.newValue);
                };
                prop.bind = function(handler) {
                    prop.changedEvent.bind(handler);
                };
                prop.unbind = function(handler) {
                    prop.changedEvent.unbind(handler);
                };
                prop.editable = function() {
                    prop.isEditable = true;
                    return prop;
                };
                prop.validate = function(callback) {
                    prop._validateCallback = callback;
                    return prop;
                };
                prop.addValidator = function(validator){
                    prop.validators.push(validator);
                    prop.validate(EventHandler(validator, validator.validate));
                    return prop;
                };
                prop.validateNewValue = function(newValue){
                    if (prop._validateCallback === undefined) {
                        return true;
                    }

                    var event = createEvent(prop.propertyName, prop.value, newValue);
                    prop._validateCallback(event);

                    var errors = {};
                    each(prop.validators, function(v){
                        var e = v.getErrors();
                        for (var i in e){
                            errors[i] = e[i];
                        }
                    });

                    return {
                        ok: event.ok,
                        errors: errors
                    };
                };
                prop.editorTemplate = function(templateSelector, templateArgument) {
                    prop.useInModel();
                    prop.editable();
                    prop.editorTemplateSelector = templateSelector;
                    prop.editorTemplateArgument = templateArgument;
                    return prop;
                };
                prop.possibleValues = function(values) {
                    if (arguments.length === 0) {
                        return prop._possibleValues;
                    }
                    prop._possibleValues = values;
                    return prop;
                };
                prop.getValueByLabel = function(label) {
                    if (!prop._possibleValues) {
                        return null;
                    }

                    for (var i in prop._possibleValues) {
                        if (prop._possibleValues[i] === label) {
                            return i;
                        }
                    }

                    return null;
                };
                prop.useInModel = function() {
                    prop.isUsedInModel = true;
                    return prop;
                };
                prop.getOwner = function(){
                    return that._owner;
                };

                //TODO: check that prop name is not a class method
                this.data[name] = this[name] = prop;

                return prop;
            },
            get : function(name) {
                //validateProperty.call(this, name);
                var prop = this.data[name];

                return prop.call(prop);
            },
            set : function(name, value) {
                //validateProperty.call(this, name);
                var prop = this.data[name];
                prop.call(prop, value);
            },
            bind : function(name, callback) {
                validateProperty.call(this, name);

                this.data[name].changedEvent.bind(callback);
            },
            unbind : function(name, callback) {
                validateProperty.call(this, name);

                this.data[name].changedEvent.unbind(callback);
            },
            lockNotifications:function() {
                this._notifications = [];
            },
            unlockNotifications:function() {
                if (this._notifications.length > 0) {
                    var that = this;
                    each(this._notifications, function(e){
                        that.data[e.property].changedEvent(e);
                    });
                    this.propertyChanged(this._notifications);
                }
                this._notifications = null;
            },
            init:function(values) {
                this.lockNotifications();
                for (var name in values) {
                    if (name[0] === '$') {
                        this.bind(name.substr(1), values[name]);
                    } else {
                        this.set(name, values[name]);
                    }
                }
                this.unlockNotifications();
            },
            getEditableProperties: function() {
                var result = [];
                for (var i in this.data) {
                    var property = this.data[i];
                    if (property.isEditable) {
                        result.push(property);
                    }
                }
                return result;
            },
            getModelProperties: function() {
                var result = [];
                iterateProperties(this, function(property, isComplex, fullName) {
                    if (!isComplex && property.isUsedInModel) {
                        result.push({name: fullName, value: property()});
                    }
                });
                return result;
            },
            copyTo: function(/*Properties*/ target) {
                iterateProperties(this, function(property, isComplex, fullName) {
                    if (fullName.indexOf(".") >= 0)
                        return;
                    if (isComplex) {
                        var sourceBag = property();
                        targetBag = target.get(property.propertyName);
                        sourceBag.copyTo(targetBag);
                    } else {
                        target.set(property.propertyName, property());
                    }
                });
            }
        }
    })());
})();